﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Core;
using System.Threading;
using System.Drawing;
using System.Diagnostics;
using Silvermoon.UI;

namespace Silvermoon.Animations
{
    public abstract class Animation
    {

        internal PropertyAccessor PropertyAccessor;
        internal Transition Transition;

        public bool IsEnqueued;
        public void Prepare(IPropertyObject obj)
        {
            PropertyAccessor.Prepare(this, obj);
        }
        public abstract void InvokeAsync();
        public abstract bool IsCompleted { get; protected set; }
        public abstract bool IsRunning { get; protected set; }
    }

    /// <summary>
    /// Abstact base class for animations.
    /// </summary>
    /// <typeparam name="T">Type of value to animate.</typeparam>
    public abstract class Animation<T> : Animation
    {
        const int BIT_FIRED = 1;
        const int BIT_COMPLETED = 2;
        const int BIT_RUNNING = 4;

        private int bitMask;
        private int startTick;

        private AnimEventArgs<T> args;

        /// <summary>
        /// Gets or sets the start value.
        /// </summary>
        public T From { get; set; }

        /// <summary>
        /// Gets or sets the end value.
        /// </summary>
        public T To { get; set; }

        /// <summary>
        /// Gets the current interpolated value.
        /// </summary>
        public T Value { get; private set; }

        /// <summary>
        /// Gets or sets the duration for the animation in ms.
        /// </summary>
        public int Duration { get; set; }

        /// <summary>
        /// Gets or sets a delay before the actual animation begins in ms.
        /// </summary>
        public int StartDelay { get; set; }


        /// <summary>
        /// Gets whether animation is completed.
        /// </summary>
        public override bool IsCompleted
        {
            get { return (bitMask & BIT_COMPLETED) != 0; }
            protected set
            {
                bitMask = value ? bitMask | BIT_COMPLETED : bitMask & ~BIT_COMPLETED;
            }
        }

        /// <summary>
        /// Gets whether animation is running.
        /// </summary>
        public override bool IsRunning
        {
            get { return (bitMask & BIT_RUNNING) != 0; }
            protected set
            {
                if (IsRunning != value)
                {
                    bitMask = value ? bitMask | BIT_RUNNING : bitMask & ~BIT_RUNNING;
                    var current = Screen.Current;
                    if (current != null)
                    {
                        if (!value)
                        {
                            current.Tick -= OnTimerTick;
                            OnCompleted();
                        }
                        else
                        {
                            current.Tick += OnTimerTick;
                        }
                    }
                }
            }
        }

        internal bool HasCompletedEvent
        {
            get { return Completed != null; }
        }

        /// <summary>
        /// Gets or sets the easing function.
        /// </summary>
        public IEasingFunction EasingFunction { get; set; }

        /// <summary>
        /// Occurs when the animation progresses.
        /// </summary>
        public event EventHandler<AnimEventArgs<T>> Progress;

        /// <summary>
        /// Occurs when the animation completes or aborts.
        /// If IsComplete is set to true, the animation completed, if IsComplete set to false, the animation was aborted.
        /// </summary>
        public event EventHandler<AnimEventArgs<T>> Completed;


        /// <summary>
        /// Initializes a new instance of the <see cref="Animation&lt;T&gt;"/> class.
        /// </summary>
        public Animation()
            : base()
        {
            args = new AnimEventArgs<T>(this);
            EasingFunction = PowerEase.Out;
        }

        /// <summary>
        /// Invokes the animation asynchronously.
        /// </summary>
        public override void InvokeAsync()
        {
            Abort();
            IsCompleted = false;
            IsRunning = true;
            startTick = Environment.TickCount;
            Prepare();
            var current = Screen.Current;

            OnProgress(0);
        }


        /// <summary>
        /// If overridden, occurs before a animation starts. this method can be used to initialize additional parameters used whilst animating.
        /// </summary>
        protected virtual void Prepare()
        {
        }

        /// <summary>
        /// Aborts a running animation without completing.
        /// </summary>
        public void Abort()
        {
            IsRunning = false;
        }

        /// <summary>
        /// Ends an animation by directly going to it's target value.
        /// </summary>
        public void End()
        {
            IsCompleted = true;
            IsRunning = false;
        }

        private void OnTimerTick(object sender, TimeEventArgs e)
        {
            int elapsed = e.Tick - startTick;
            if (elapsed >= StartDelay)
            {
                elapsed -= StartDelay;
                if (elapsed > Duration) elapsed = Duration;
                OnProgress(elapsed);
            }
            else OnProgress(0);
        }

        /// <summary>
        /// Occurs when the animation is in progress.
        /// </summary>
        /// <param name="elapsed">The elapsed time, exclusive StartDelay.</param>
        protected virtual void OnProgress(int elapsed)
        {
          //  if (!IsRunning) return;
            if (elapsed == Duration)
            {
                Value = To;
            }
            else if (elapsed == 0)
            {
                Value = From;
            }
            else
            {
                float t = (float)elapsed / (float)Duration;
                float value = EasingFunction.Ease(t);
                Value = CalculateValue(value);
            }
            IsCompleted = elapsed >= Duration;
            if (Progress != null) Progress(this, args);
            if (IsCompleted) Abort();
        }

        /// <summary>
        /// Occurs when the animation completed or aborted.
        /// </summary>
        protected virtual void OnCompleted()
        {
            if (Completed != null)
            {
                Completed(this, args);
                Completed = null;
                //ClearDelegates();
            }
        }

        protected abstract T CalculateValue(float value);

        public static Animation<T> CreateAnimation()
        {
            Type t = typeof(T);
            if (typeof(int) == t) return new IntAnimation() as Animation<T>;
            if (typeof(float) == t) return new FloatAnimation() as Animation<T>;
            if (typeof(bool) == t) return new BoolAnimation() as Animation<T>;
            if (typeof(Color) == t) return new ColorAnimation() as Animation<T>;
            if (typeof(Size) == t) return new SizeAnimation() as Animation<T>;
            if (typeof(Point) == t) return new PointAnimation() as Animation<T>;
            if (typeof(Rectangle) == t) return new RectangleAnimation() as Animation<T>;
            if (typeof(TranslateTransform) == t) return new TranslateAnimation() as Animation<T>;
            if (typeof(ScaleTransform) == t) return new ScaleAnimation() as Animation<T>;
            if (typeof(PointF) == t) return new PointFAnimation() as Animation<T>;
            throw new NotSupportedException();
        }

        public object Tag;

        public void ClearDelegates()
        {
            Completed = null;
            Progress = null;

        }

    }

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class AnimEventArgs<T> : EventArgs
    {
        private Animation<T> animation;

        internal AnimEventArgs(Animation<T> animation)
            : base()
        {
            this.animation = animation;
        }

        /// <summary>
        /// Gets the duration of the animation.
        /// </summary>
        public int Duration;

        /// <summary>
        /// Gets whether the animation is completed.
        /// </summary>
        public bool IsCompleted { get { return animation.IsCompleted; } }

        /// <summary>
        /// Gets the current interpolated value.
        /// </summary>
        public T Value { get { return animation.Value; } }

        /// <summary>
        /// Gets or sets the start value.
        /// </summary>
        public T From
        {
            get { return animation.From; }
            set { animation.From = value; }
        }

        /// <summary>
        /// Gets or sets the end value.
        /// </summary>
        public T To
        {
            get { return animation.To; }
            set { animation.To = value; }
        }
    }
}
